package pira.db.ui;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.ReturnableEvaluator;
import org.neo4j.graphdb.StopEvaluator;
import org.neo4j.graphdb.Traverser;
import org.neo4j.graphdb.Traverser.Order;
import org.neo4j.graphdb.index.RelationshipIndex;

import pira.elements.Author;
import pira.elements.Paper;
import pira.parser.xml.dblp.XMLFilesReader;
import pira.parser.xml.dblp.XMLFilesReader.MyRelationshipTypes;
import pira.utils.Log;

public class MinimumDBUI extends SubDBUI {
	
	// default database path
//	public static final String DEFAULTDATABASE = "minimumDb/";
	public static final String DEFAULTDATABASE = "../DB/completeDB_with_PersonCite/";

	// use to open standard output
	public static final boolean D = false;
	
	public enum Neo4jRelationTypes implements RelationshipType {
		WROTE, 
		WRITTEN_BY,
		PERSON_CITE,
		CITE, 
		COAUTHOR,
		UNKNOWN
    }
	
	public MinimumDBUI() {
		super(DEFAULTDATABASE, CONFIG);
	}

	public void changeRelationType() {
		tx = graphDb.beginTx();
		long count = 0;
		long citeCount = 0;
		long writeCount = 0;
		long wroteCount = 0;
		long wByCount = 0;
		try {
			Iterable<Node> all = graphDb.getAllNodes();
			for (Node node : all) {
				Iterable<Relationship> outs = node.getRelationships(Direction.OUTGOING);
				for (Relationship r : outs) {
//					Node endNode = r.getEndNode();
					if (r.getType().equals(Neo4jRelationTypes.CITE)) {
						count++;
						citeCount++;
					} else if (r.getType().equals(MyRelationshipTypes.WRITE)) {
						count++;
						writeCount++;
					} else if (r.getType().equals(Neo4jRelationTypes.WRITTEN_BY)) {
						count++;
						wByCount++;
					} else if (r.getType().equals(Neo4jRelationTypes.WROTE)) {
						count++;
						wroteCount++;
					}
					
//					if (count % 10000 == 0 ) {
//						System.out.println(count);
//						tx.success();
//						tx.finish();
//						tx = graphDb.beginTx();
//					}
				}
			}
			System.out.println("relationship number : " + count);
			System.out.println("Cite count : " + citeCount);
			System.out.println("Write count : " + writeCount);
			System.out.println("Wrote count : " + wroteCount);
			System.out.println("Written By count : " + wByCount);
			tx.success();
		} finally {
			tx.finish();
		}
		
		Iterable<RelationshipType> types = graphDb.getRelationshipTypes();
		for (RelationshipType type : types) {
			System.out.println(type.toString());
		}
	}
	
	// method used to calculate the h-index value
	public void calculHIndex() {
		long startTime = System.currentTimeMillis();
		Log hLog = new Log("outputs/Infos_Of_Author.txt");
		String str = "Order\tAuthorID\tAuthorName\tCitation Number\t" +
				"Publication Number\tH-Index\n";
		hLog.write(str);
//		System.out.print(str);
		
		long authorID = BEGINAUTHORID;
		tx = graphDb.beginTx();
		try {
			int authorCount = 0;

			
			while (authorID < ENDAUTHORID) {
				Node author = graphDb.getNodeById(authorID);
				if (author.hasProperty(Author.FULLNAME)) {
					authorCount++;
					str = authorCount + "\t" + authorID + "\t" +
						author.getProperty(Author.FULLNAME) + "\t";
//					System.out.print(str);
					hLog.write(str);
					
					calculateAuthorHIndex(author, authorID, hLog);
				}
				authorID++;
				
				if (authorCount % 10000 == 0) {
					System.out.println(authorCount);
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
			}
			str = "In total, there're " + authorCount + " authors.\n";
			System.out.print(str);
			
//			hLog.write(str);
			tx.success();
		} finally {
			hLog.close();
			tx.finish();
		}
		
		long timePassed = System.currentTimeMillis() - startTime;
		System.out.println("Run " + timePassed/1000 + " seconds.");
	}
	
	private void calculateAuthorHIndex(Node author, long authorID, Log hLog) {
		
		// algorithm for calculate H-Index
		String str = null;

		int publicationNumber = (Integer) author.getProperty(Author.PUBLICATIONNUMBER);
		int i = 1;
		for (i= 1; i <= publicationNumber; i++) {
			Iterator<Relationship> rels = author.getRelationships(
					XMLFilesReader.MyRelationshipTypes.WRITE).iterator();
			int count = 0;
			while (rels.hasNext()) {
				Node paper = rels.next().getEndNode();

				int size = 0;
				
				Iterator<Relationship> myCites = paper.getRelationships(
						MyRelationshipTypes.CITE, Direction.INCOMING).iterator();
				
				while (myCites.hasNext()) {
					myCites.next();
					size++;
				}
				
				if (size >= i)
					count++;
				if (count >= i)
					break;
			}

			if (count < i)
				break;
		}

		author.setProperty(Author.HINDEX, (i - 1));
		
		str =  author.getProperty(Author.CITATIONNUMBER) + "\t" 
			+  author.getProperty(Author.PUBLICATIONNUMBER) + "\t"
			+  author.getProperty(Author.HINDEX) +"\n";
//		System.out.print(str);
		hLog.write(str);
	}
	
	
	// copy nodes
	private void copyNodes(SubDBUI sdbui) {
		tx = graphDb.beginTx();
		try {
			this.createGlobalNode();
			this.createExternalNode();
			Iterator<Node> all = sdbui.getGraphDatabaseService().getAllNodes()
					.iterator();
			all.next();
			long id = -1;
			while (all.hasNext()) {
				Node node = all.next();
				if (node.getId() == GLOBALNODEID
						|| node.getId() == EXTERNALNODEID)
					continue;
				if (node.hasProperty(Paper.TITLE)) {
					Node n = graphDb.createNode();
					this.paperNumber++;
					n.setProperty(Paper.TITLE, node.getProperty(Paper.TITLE));
					id = n.getId();
				} else {
					Node n = graphDb.createNode();
					this.authorNumber++;
					n.setProperty(Author.FULLNAME,
							node.getProperty(Author.FULLNAME));
					id = n.getId();
				}
				if (id % 10000 == 0) {
					System.out.println(id);
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
			}
			Node gNode = graphDb.getNodeById(GLOBALNODEID);
			gNode.setProperty(ALLNODENUMBER, paperNumber + authorNumber + 2);
			gNode.setProperty(PAPERNUMBER, paperNumber);
			gNode.setProperty(AUTHORNUMBER, authorNumber);
			tx.success();
		} finally {
			tx.finish();
		}
System.out.println("Copy nodes finished.");
	}

	// copy relationships
	private void copyRelationships(SubDBUI sdbui) {
		tx = graphDb.beginTx();
		try {
			Node oldStartNode = null;
			Node oldEndNode = null;
			Node newStartNode = null;
			Node newEndNode = null;
			long startNodeId = -1, endNodeId = -1;
			long count = 0;
			for (org.neo4j.graphdb.Relationship r : sdbui.writes.query(SubDBUI.INDEX, "*")) {
				oldStartNode = r.getStartNode();
				oldEndNode = r.getEndNode();
				startNodeId = oldStartNode.getId();
				endNodeId = oldEndNode.getId();
				newStartNode = graphDb.getNodeById(startNodeId);
				newEndNode = graphDb.getNodeById(endNodeId);
				
				newStartNode.createRelationshipTo(newEndNode, MyRelationshipTypes.WRITE);
				
				count++;
				if (count % 10000 == 0) {
					System.out.println(count);
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
				
			}
			
			for (org.neo4j.graphdb.Relationship r : sdbui.cites.query(SubDBUI.INDEX, "*")) {
				oldStartNode = r.getStartNode();
				oldEndNode = r.getEndNode();
				startNodeId = oldStartNode.getId();
				endNodeId = oldEndNode.getId();
				newStartNode = graphDb.getNodeById(startNodeId);
				newEndNode = graphDb.getNodeById(endNodeId);
				
				newStartNode.createRelationshipTo(newEndNode, MyRelationshipTypes.CITE);
				
				count++;
				if (count % 10000 == 0) {
					System.out.println(count);
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
			}
			
			tx.success();
		} finally {
			tx.finish();
		}
	}
	
	public void generateMinimumDB(SubDBUI sdbui) {
		this.copyNodes(sdbui);
		this.copyRelationships(sdbui);
	}
	
	public void addIndexForPapersAndAuthors() {
		tx = graphDb.beginTx();
		try {
			Iterator<Node> all = graphDb.getAllNodes().iterator();
			all.next();
			Node node = null;
			while (all.hasNext()) {
				node = all.next();
				if (node.getId() == GLOBALNODEID || node.getId() == EXTERNALNODEID)
					continue;
				
				if (node.hasProperty(Paper.TITLE)) {
//					papers.add(node, Paper.TITLE, normal((String)node.getProperty(Paper.TITLE)));
					papers.add(node, Author.DBID, node.getId());
				} 
//				else
//					authors.add(node, Author.NAME, normal((String)node.getProperty(Author.FULLNAME)));
				
				if (node.getId() % 10000 == 0) {
					System.out.println(node.getId());
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
			}
			tx.success();
		} finally {
			tx.finish();
		}
	}
	
	/*
	 * This method calculate the number of citation and the number of publication
	 * 		for a author and update information to database
	 * Log files used: Citation_number_for_authors.txt
	 */
	public void calculateCitaNumAndPubliNumOfAuthor() {
		Log log = new Log("outputs/Citation_number_for_authors.txt");
		tx = graphDb.beginTx();
		try {
			// record how many authors has been traversed
			long authorID = BEGINAUTHORID;
			int authorCount = 0;
			while (authorID < ENDAUTHORID) {
				Node author = graphDb.getNodeById(authorID);
				if (author.hasProperty(Author.FULLNAME)) {
					authorCount++;

					int citationNumber = 0;
					int publicationNumber = 0;
					String str = null;
					String name = (String) author.getProperty(Author.FULLNAME);
					str = "The " + authorCount + " th author : " + name
							+ "\tID: " + authorID + "\n";
					if (D) System.out.print(str);
					log.write(str);

					// get all the papers that he writes
					Iterator<Relationship> wrs = author.getRelationships(
							XMLFilesReader.MyRelationshipTypes.WRITE,
							Direction.OUTGOING).iterator();

					while (wrs.hasNext()) {
						
						publicationNumber++;
						// get the paper
						Node p = wrs.next().getEndNode();
						str = p.getId() + " : "
								+ (String) p.getProperty(Paper.TITLE) + "\n";
						if (D) System.out.print(str);
						log.write(str);

						// get all the papers that cite this paper
						Iterator<Relationship> cs = p.getRelationships(
								XMLFilesReader.MyRelationshipTypes.CITE,
								Direction.INCOMING).iterator();

						if (cs.hasNext()) {
							str = "=========> paper who cite this: \n";
							if (D) System.out.print(str);
							log.write(str);
						}

						int i = 1;
						while (cs.hasNext()) {
							Relationship rel = cs.next();

							// get the cite paper
							Node cp = rel.getStartNode();
							String t = (String) cp.getProperty(Paper.TITLE);

							str = i + " : " + t + "\n";
							if (D) System.out.print(str);
							log.write(str);
							i++;
							citationNumber++;
						}

					}

					str = "Citation Number for the author is : "
							+ citationNumber + "\n";
					if (D) System.out.print(str);
					log.write(str);
					
					str = "Publication Number for the author is :" 
							+ publicationNumber + "\n\n";
					if (D) System.out.print(str);
					log.write(str);
					
					author.setProperty(Author.CITATIONNUMBER, citationNumber);
					author.setProperty(Author.PUBLICATIONNUMBER, publicationNumber);
				}
				authorID++;
				
				if (authorCount % 10000 == 0) {
					if (D) System.out.println(authorCount);
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
			}
			tx.success();
		} finally {
			log.close();
			tx.finish();
		}
	}

	/*
	 *  Calculate the page rank for every author
	 */
	public void calculatePageRankOfAuthor() {
		Log log = new Log("outputs/Page_Rank_for_authors.txt");
		tx = graphDb.beginTx();
		try {
			// traverse every author
			int count = 0;
			for (Node author : authors.query(Author.NAME, "*")) {
				// my page rank value
				Double myPageRank = (double) 0;
				
				// traverse every paper the author writes
				Iterable<Relationship> myWrites = author.getRelationships(
						MyRelationshipTypes.WRITE, Direction.OUTGOING);
				Node myPaper = null;
				for (Relationship w : myWrites) {
					myPaper = w.getEndNode();
					Double pr = (Double) myPaper.getProperty(Paper.PAGERANK);
					
					// get the co-author number of the paper
					Iterable<Relationship> coAuthors = myPaper.getRelationships(
							MyRelationshipTypes.WRITE, Direction.INCOMING);
					int nbOfcoauthor = 0;
					for (@SuppressWarnings("unused") Relationship c : coAuthors)
						nbOfcoauthor++;
					
					myPageRank += (pr / nbOfcoauthor);
				}
				
				author.setProperty(Author.PAGERANK, myPageRank);
				count++;
				
				log.write(count + "\t" + author.getId() + "\t" + myPageRank + 
						"\t" + author.getProperty(Author.FULLNAME) + "\n");
				
				if (count % 10000 == 0) {
					System.out.println(count);
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
				
			}
			System.out.println("Totally " + count + " authors.");
			tx.success();
		} finally {
			tx.finish();
		}
	}
	
	/*
	 *  Load papers' page rank from file : Page_Rank_without_title.txt
	 */
	public void readPageRankFromFile() {
		tx = graphDb.beginTx();
		try {
			FileReader fr = new FileReader(new File("outputs/Page_Rank_without_title.txt"));
			BufferedReader br = new BufferedReader(fr);
			String line = null;
			long id = -1;
			Double pr = null;
			Node paper = null;
			int count = 0;
			while (br.ready()) {
				line = br.readLine();
				id = Long.parseLong(line.split("\t")[1]);
				pr = Double.parseDouble(line.split("\t")[2]);
				
				paper = graphDb.getNodeById(id);
				if (!paper.hasProperty(Paper.TITLE)) {
					tx.failure();
					tx.finish();
					System.err.println("The " + id + " is not a paper!");
					System.exit(1);
				}
				
				paper.setProperty(Paper.PAGERANK, pr);
				
				count++;
				if (count % 10000 == 0) {
					System.out.println(count);
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
			}
			br.close();
			fr.close();
			tx.success();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			tx.finish();
		}
	}
	
	/*
	 *  Load authors' rank : outputs/Author_Infors_full.txt
	 */
	public void loadAuthorsRank() {
		tx = graphDb.beginTx();
		try {
			FileReader fr = new FileReader(new File("outputs/Author_Infors_full.txt"));
			BufferedReader br = new BufferedReader(fr);
			String line = null;
			
			long id = -1;
			Double piraIndex = null;
			long citaOrder = -1, prOrder = -1, 
					piraOrder = -1, pubOrder = -1, hIndexOrder = -1;
			
			Node author = null;
			int count = 0;
			String[] strs = null;
			
			// skip the title
			br.readLine();
			
			while (br.ready()) {
				line = br.readLine();
				strs = line.split("\t");

				id = Long.parseLong(strs[0]);
				
				if (strs[6].charAt(0) == '"') {
					String tmp = strs[6].substring(1, strs[6].length()-1);
					piraIndex = Double.parseDouble(tmp);
				} else
					piraIndex = Double.parseDouble(strs[6]);
				
				
				citaOrder = Long.parseLong(strs[7]);
				prOrder = Long.parseLong(strs[8]);
				piraOrder = Long.parseLong(strs[9]);
				pubOrder = Long.parseLong(strs[10]);
				hIndexOrder = Long.parseLong(strs[11]);
				
//				System.out.println(id + "\t" + piraIndex + "\t" + citaOrder + "\t" + prOrder + "\t" + piraOrder + "\t" + pubOrder + "\t" + hIndexOrder);
				
				author = graphDb.getNodeById(id);
				if (!author.hasProperty(Author.FULLNAME)) {
					tx.failure();
					tx.finish();
					System.err.println("The " + id + " is not a author!");
					System.exit(1);
				}
				
				author.setProperty(Author.PIRAINDEX, piraIndex);
				author.setProperty(Author.CITATIONRANK, citaOrder);
				author.setProperty(Author.PAGERANKRANK, prOrder);
				author.setProperty(Author.PIRARANK, piraOrder);
				author.setProperty(Author.PUBLICATIONORDER, pubOrder);
				author.setProperty(Author.HINDEXORDER, hIndexOrder);
				
//				authors.add(author, Author.DBID, id);
				
				count++;
				if (count % 10000 == 0) {
					System.out.println(count);
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
			}
			br.close();
			fr.close();
			tx.success();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			tx.finish();
		}
	}
	
	/*
	 *  Load papers' rank from file : outputs/Paper_Infors.txt
	 */
	public void loadPapersRank() {
		tx = graphDb.beginTx();
		try {
			FileReader fr = new FileReader(new File("outputs/Paper_Infors.txt"));
			BufferedReader br = new BufferedReader(fr);
			String line = null;
			
			// skip the title
			br.readLine();
			
			long rankOfCita = -1, rankOfPR = -1;
			long id = -1;
			Node paper = null;
			int count = 0;
			String[] strs = null;
			while (br.ready()) {
				line = br.readLine();
				strs = line.split("\t");
				id = Long.parseLong(strs[0]);
				rankOfCita = Long.parseLong(strs[5]);
				rankOfPR = Long.parseLong(strs[6]);
				
//				System.out.println(id + "\t" + rankOfCita + "\t" + rankOfPR);

				paper = graphDb.getNodeById(id);
				
				paper.setProperty(Paper.CITATIONRANK, rankOfCita);
				paper.setProperty(Paper.PAGERANKRANK, rankOfPR);
				
				count++;
				if (count % 10000 == 0) {
					System.out.println(count);
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
			}
			
			br.close();
			fr.close();
			tx.success();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			tx.finish();
		}
	}
	
	/*
	 *  Load paper pira index : outputs/Papers_ranking_v2.txt
	 */
	public void readPiraIndexOfPaper() {
		tx = graphDb.beginTx();
		try {
			FileReader fr = new FileReader(new File("outputs/Papers_ranking_v2.txt"));
			BufferedReader br = new BufferedReader(fr);
			String line = null;
			long id = -1;
			Double pr = null;
			Node paper = null;
			long count = 1;
			
			// skip the title
			br.readLine();
			while (br.ready()) {
				line = br.readLine();
				id = Long.parseLong(line.split("\t")[0]);
				pr = Double.parseDouble(line.split("\t")[2]);
				
				System.out.println(id + "\t" + pr);
				
				paper = graphDb.getNodeById(id);
				if (!paper.hasProperty(Paper.TITLE)) {
					tx.failure();
					tx.finish();
					System.err.println("The " + id + " is not a paper!");
					System.exit(1);
				}
				
				// calculate citation times
				Iterable<Relationship> myCiteds = paper.getRelationships(
						MyRelationshipTypes.CITE, Direction.INCOMING);
				int citedTimes = 0;
				for (@SuppressWarnings("unused") Relationship c : myCiteds)
					citedTimes++;
				
				paper.setProperty(Paper.CITEDTIMES, citedTimes);
				paper.setProperty(Paper.PIRAINDEX, pr);
				paper.setProperty(Paper.PIRARANK, count);
				
				count++;
				if (count % 10000 == 0) {
					System.out.println(count);
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
			}
			br.close();
			fr.close();
			tx.success();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			tx.finish();
		}
	}
	
	
	/*
	 *  Distinguish the nodes who come from DBLP
	 */
	public void addDBLPProperty(SubDBUI sdbui) {
		Iterable<Node> all = sdbui.graphDb.getAllNodes();
		int count = 0;
		tx = graphDb.beginTx();
		try {
			for (Node subNode : all) {
				if (subNode.getId() == 0 || subNode.getId() == EXTERNALNODEID
						|| subNode.getId() == GLOBALNODEID)
					continue;

				long id = subNode.getId();
				String type = null;
				// for every paper
				if (subNode.hasProperty(Paper.TITLE)) {
					type = (String) subNode.getProperty(Paper.TYPE);
					if (!type.equals(NOT_EXIST)) {
//						System.out.println(id);
						count++;

						Node miniNode = graphDb.getNodeById(id);

						// add DBLP property to this node and all his authors
						miniNode.setProperty(Paper.DBLP, true);

						Iterable<Relationship> rels = miniNode
								.getRelationships(MyRelationshipTypes.WRITE,
										Direction.INCOMING);
						for (Relationship w : rels) {
							Node a = w.getStartNode();
							a.setProperty(Author.DBLP, true);
//							System.out.println("author : " + a.getId());
						}
					}
				}
				
				if (count % 10000 == 0) {
					System.out.println(count);
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
			}
			System.out.println("Count = " + count);
			tx.success();
		} finally {
			tx.finish();
		}
	}
	
	// count self cite counts and cycles of two nodes
	public void countCycles() {
		int selfCiteCount = 0, cycleOf2Count = 0;
		Log selfCite = new Log("SELF_CITE.txt");
		Log cycleCite = new Log("CYCLE_CITE.txt");
		HashSet<String> cycleCiteIDs = new HashSet<String>();
		for (Node paper : papers.query(Paper.DBID, "*")) {
			Iterable<Relationship> citeOuts = paper.getRelationships(MyRelationshipTypes.CITE, Direction.OUTGOING);
			for (Relationship r : citeOuts) {
				Node endNode = r.getEndNode();
				if (endNode.getId() == paper.getId()) {
					selfCiteCount++;
					selfCite.write(paper.getId() + "\n");
				} else {
					boolean citeBack = false;
					Iterable<Relationship> endCiteOuts = endNode.getRelationships(MyRelationshipTypes.CITE, Direction.OUTGOING);
					for (Relationship endCite : endCiteOuts	) {
						if (endCite.getEndNode().getId() == paper.getId()) {
							citeBack = true;
							break;
						}
					}
					
					if (citeBack) {
						String IDs = null;
						if (paper.getId() < endNode.getId())
							IDs = paper.getId() + "," + endNode.getId();
						else
							IDs = endNode.getId() + "," + paper.getId();
						
						boolean find = false;
						Iterator<String> idIter = cycleCiteIDs.iterator();
						while (idIter.hasNext() && !find) {
							String str = idIter.next();
							if (str.equals(IDs))
								find = true;
						}
						
						if (!find) {
							cycleCiteIDs.add(IDs);
							cycleOf2Count++;
							cycleCite.write(IDs + "\n");
						}
					}
				}
			}
		}

		selfCite.write("\nSelf Count = " + selfCiteCount);
		cycleCite.write("\nCycle Cite of 2 = " + cycleOf2Count);
		selfCite.close();
		cycleCite.close();
		System.out.println("Self cite count = " + selfCiteCount);
		System.out.println("Cycle cite count = " + cycleOf2Count);
	}
	
	public void countAuthorHomonyms() {
		try {
			long startTime = System.currentTimeMillis();
//			String PERSONCITES = "personCites";
//			RelationshipIndex personCites = index.forRelationships(PERSONCITES);
			FileReader fr = new FileReader(new File("outputs/Author_Homonym.txt"));
			BufferedReader br = new BufferedReader(fr);
			Log citationLog = new Log("Citation_Papers_Log.txt");
			Log coauthorLog = new Log("Coauthor_Papers_Log.txt");
			Log exceptionLog = new Log("Exception_Papers_Log.txt");
			Log errors = new Log("Errors_Log.txt");
			Log merge = new Log("Merge_Log.txt");
			String line = null;
			int exceptionCount = 0;
			
			while (br.ready()) {
				line = br.readLine();
				String[] strs = line.split("\t");
				if (strs.length > 1) {
					long order = Long.parseLong(strs[0]);
					long id1 = Long.parseLong(strs[1]);
					long id2 = Long.parseLong(strs[3]);
					String fullname1 = strs[2];
					String fullname2 = strs[4];
					
					Node author1 = graphDb.getNodeById(id1);
					Node author2 = graphDb.getNodeById(id2);
					
					Node initial, complete = null;
					
					// judge if a1 or a2 begin with initial letter
					String firstName1 = getFirstName(fullname1, errors);
					String firstName2 = getFirstName(fullname2, errors);
					if (firstName1.length() == 1 || (firstName1.length() == 2 && firstName1.charAt(1) == '.')) {
						// author1 is initial
						initial = author1;
						complete = author2;
					} else if (firstName2.length() == 1 || (firstName2.length() == 2 && firstName2.charAt(1) == '.')) {
						// author2 is initial
						initial = author2;
						complete = author1;
					} else {
						merge.write(fullname1 + " and " + fullname2 + " are different.\n");
						continue;
					}
					
					// judge if a1 and a2 are coauthors
					boolean isCoauthors = false;
					long coPaper = -1;
					for (Relationship r : author1.getRelationships(Neo4jRelationTypes.COAUTHOR)) {
						if (r.getOtherNode(author1).getId() == id2) {
							isCoauthors = true;
							coPaper = (Long) r.getProperty(Paper.DBID);
							break;
						}
					}
					
					// if they are coauthors, continue next;
					if (isCoauthors) {
						exceptionLog.write("CO\t(" + fullname1 + "," + id1 + ") and (" + fullname1 + "," + id2 + ") are coauthors (" + coPaper + ").\n");
						continue;
					}
					
					// begin to find mergeable nodes.
					HashSet<Node> citationPapers = new HashSet<Node>();
					HashSet<Node> authorPapers = new HashSet<Node>();
					HashSet<Node> otherPapers = new HashSet<Node>();
					HashSet<Node> coauthorsOfA1 = new HashSet<Node>();
					HashSet<Node> papersOfA2 = new HashSet<Node>();
					
					// initialization for coauthorsOfA1
					for (Relationship r : complete.getRelationships(Neo4jRelationTypes.COAUTHOR, Direction.OUTGOING)) {
						coauthorsOfA1.add(r.getEndNode());
					}
					
					// initialization for ciatationPapers
					Iterator<Relationship> myPersonCites = complete.getRelationships(Neo4jRelationTypes.PERSON_CITE).iterator();
					while (myPersonCites.hasNext()) {
						Relationship r = myPersonCites.next();
						Node other = r.getOtherNode(complete);
						if (other.getId() == initial.getId()) {
							String index = (String) r.getProperty(INDEX);
							// get the index of IDs
							String[] IDs = index.split("_");
							// if IDs[0] is the id of complete, 
							// then complete -- personCite --> initial 
							if (Long.parseLong(IDs[0]) == complete.getId()) {
								Node p = graphDb.getNodeById(Long.parseLong(IDs[2]));
								citationPapers.add(p);
								citationLog.write(0 + "\t" + fullname1 + "\t" + fullname2 + "\t" + index + "\n");
							} else if (Long.parseLong(IDs[3]) == complete.getId()) {
								// initial -- personCite --> complete
								Node p = graphDb.getNodeById(Long.parseLong(IDs[1]));
								citationPapers.add(p);
								citationLog.write(0 + "\t" + fullname1 + "\t" + fullname2 + "\t" + index + "\n");
							} else {
								errors.write("Errors at : " + fullname1 + "\t" + fullname2 + "\t" + index + "\n");
							}
						} 
					}
					
					// initialization for authorPapers
					for (Relationship r : initial.getRelationships(Neo4jRelationTypes.COAUTHOR, Direction.OUTGOING)) {
						Node coauthor = r.getEndNode();
						if (coauthorsOfA1.contains(coauthor)) {
							Node pNode = graphDb.getNodeById((Long) r.getProperty(Paper.DBID));
							coauthorLog.write("(" + complete.getProperty(Author.FULLNAME) + "," + complete.getId() + 
									")\t(" + initial.getProperty(Author.FULLNAME) + "," + initial.getId() + 
									")\t(" + coauthor.getProperty(Author.FULLNAME) + "," + coauthor.getId() +
									")\t(" + pNode.getId() + ")\n");
							authorPapers.add(pNode);
						}
					}
					
					// initialization for papersOfA2 and otherPapers
					for (Relationship r : initial.getRelationships(Neo4jRelationTypes.WROTE, Direction.OUTGOING)) {
						Node p = r.getEndNode();
						papersOfA2.add(p);
						if (!citationPapers.contains(p) && !authorPapers.contains(p))
							otherPapers.add(p);
					}
					
					
					// initialization for newAddPapers and newAddAuthors
					Set<Node> newAddPapers = new HashSet<Node>();
					for (Node node : citationPapers)
						newAddPapers.add(node);
					for (Node node : authorPapers)
						if (!newAddPapers.contains(node))
							newAddPapers.add(node);
							
					Set<Node> newAddAuthors = new HashSet<Node>();
					for (Node node : coauthorsOfA1)
						newAddAuthors.add(node);
							
					// recursion for all nodes
					do {
						Set<Node> thisAddPapers = new HashSet<Node>();
						Set<Node> thisAddAuthors = new HashSet<Node>();
						
						for (Node p : otherPapers) {
							for (Relationship r : p.getRelationships(Neo4jRelationTypes.CITE)) {
								Node node = r.getOtherNode(p);
								if (newAddPapers.contains(node)) {
									citationLog.write(1 + "\t" + complete.getProperty(Author.FULLNAME) + "\t" + 
											initial.getProperty(Author.FULLNAME) + "\t" + complete.getId() + "_" + 
											initial.getId() + "_" + p.getId() + "_" + node.getId() + "\n");
									thisAddPapers.add(p);
									citationPapers.add(p);
									break;
								}
							}
							
							for (Relationship r : p.getRelationships(Neo4jRelationTypes.WROTE, Direction.INCOMING)) {
								Node authorNode = r.getStartNode();
								if (authorNode.getId() != initial.getId()) {
									if (newAddAuthors.contains(authorNode)) {
										coauthorLog.write("(" + complete.getProperty(Author.FULLNAME) + "," + complete.getId() + 
												")\t(" + initial.getProperty(Author.FULLNAME) + "," + initial.getId() + 
												")\t(" + authorNode.getProperty(Author.FULLNAME) + "," + authorNode.getId() +
												")\t(" + p.getId() + ")\n");
										thisAddAuthors.add(p);
										authorPapers.add(p);
										break;
									}
								}
							}
							
						}
						
						newAddPapers.clear();
						newAddAuthors.clear();
						
						for (Node node : thisAddPapers) {
							otherPapers.remove(node);
							newAddPapers.add(node);
						}
						
						for (Node pNode : thisAddAuthors) {
							otherPapers.remove(pNode);
							for (Relationship r : pNode.getRelationships(Neo4jRelationTypes.WROTE, Direction.INCOMING)) {
								Node aNode = r.getStartNode();
								if (aNode.getId() != initial.getId() && !coauthorsOfA1.contains(aNode)) {
									newAddAuthors.add(aNode);
								}
							}
						}
					} while (!newAddPapers.isEmpty() && !newAddAuthors.isEmpty() && !otherPapers.isEmpty());
					
					// third loop, getting exception papers
					for (Node p : papersOfA2) {
						if (!citationPapers.contains(p) && !authorPapers.contains(p)) {
							exceptionLog.write(complete.getProperty(Author.FULLNAME) + "\t" + initial.getProperty(Author.FULLNAME) + "\t" + p.getId() + "\n");
							exceptionCount++;
						}
					}
					
					if (order % 10000 == 0) {
						System.out.println("The " + order + "th item\t" + (System.currentTimeMillis()-startTime)/1000 + "s\t" + order * 100 /107399 + "%" );
					}
				}
			}
			System.out.println("Totally there are " + exceptionCount + " exception papers.");
			citationLog.close();
			coauthorLog.close();
			exceptionLog.close();
			errors.close();
			merge.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private Traverser getAuthorsTraverser(final Node paper) {
		return paper.traverse(Order.BREADTH_FIRST, StopEvaluator.DEPTH_ONE,
				ReturnableEvaluator.ALL_BUT_START_NODE,
				Neo4jRelationTypes.WROTE, Direction.INCOMING);
	}
	
	public void addCoauthor() {
		int paperCount = 0;
		int coauthorCount = 0;
		long startTime = System.currentTimeMillis();
		tx = graphDb.beginTx();
		try {
			tx.success();
			for (Node myPaper : papers.query(Paper.DBID, "*")) {
				Iterable<Relationship> myWrotes = myPaper.getRelationships(Neo4jRelationTypes.WROTE, Direction.INCOMING);
				for (Relationship w1 : myWrotes) {
					Node author1 = w1.getStartNode();
					Iterable<Relationship> coauthors = myPaper.getRelationships(Neo4jRelationTypes.WROTE, Direction.INCOMING);
					for (Relationship w2 : coauthors) {
						Node author2 = w2.getStartNode();
						if (author1.getId() != author2.getId()) {
							Relationship co = author1.createRelationshipTo(author2, Neo4jRelationTypes.COAUTHOR);
							co.setProperty(Paper.DBID, myPaper.getId());
							coauthorCount++;
						}
					}
				}
				
				paperCount++;
				if (paperCount % 10000 == 0) {
					System.out.println("Paper count : " + paperCount);
					System.out.println("Coauthor count : " + coauthorCount);
					System.out.println("Time elapsed : " + (System.currentTimeMillis()-startTime)/1000 + "s.\t" + paperCount*100/281207 + "%.\n");
					
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
				
			}
		} finally {
			tx.finish();
		}
	}
	
	private String getFirstName(String fullName, Log log) {
		String[] strs = SubDBUI.normal(fullName).split(" ");
		String firstName = "";
		int len = strs.length;
		if (len >= 2) 
			firstName = strs[0].trim();
		else {
			log.write(fullName + " has no first name.\n");
		}
		return firstName;
	}
	
	public void addPersonCite() {
		String PERSONCITES = "personCites";
		int personCiteCount = 0;
		int paperCount = 0;
		long startTime = System.currentTimeMillis();
		tx = graphDb.beginTx();
		try {
			RelationshipIndex personCites = index.forRelationships(PERSONCITES); 
			// traverse every paper in DB
			for (Node myPaper : papers.query(Paper.DBID, "*")) {
				// if my paper cites other papers 
				if (myPaper.hasRelationship(Neo4jRelationTypes.CITE, Direction.OUTGOING)) {
					// get every author for the paper
					Traverser myAuthors = this.getAuthorsTraverser(myPaper);
					// get papers cited by me
					Iterable<Relationship> myCitations = myPaper.getRelationships(Neo4jRelationTypes.CITE, Direction.OUTGOING);
					
					// traverse every citation
					for (Relationship citation : myCitations) {
						Node citedPaper = citation.getEndNode();
						// add person cite relationships
						for (Node myAuthor : myAuthors) {
							// get authors of the cited paper
							Traverser citedAuthors = this.getAuthorsTraverser(citedPaper);
							for (Node citedAuthor : citedAuthors) {
								Relationship personCite = myAuthor.createRelationshipTo(citedAuthor, Neo4jRelationTypes.PERSON_CITE);
								personCiteCount++;
								String value = myAuthor.getId() + "_" + myPaper.getId() + "_" + citedPaper.getId() + "_" + citedAuthor.getId();
								personCite.setProperty(INDEX, value);
								personCites.add(personCite, INDEX, value);
							}
						}
					}
				}
				
				paperCount++;
//				System.out.println(myPaper.getId());
				if (paperCount % 10000 == 0) {
					System.out.println("paper count = " + paperCount);
					System.out.println("person cite count = " + personCiteCount);
					System.out.println("Time : " + (System.currentTimeMillis() - startTime)/1000 + "s.\t" + paperCount * 100 /281207 + "%.\n");
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
			}
			tx.success();
		} finally {
			tx.finish();
		}
	}
}
